package cn.wolfcode.service.impl;

import cn.wolfcode.common.util.AssertUtils;
import cn.wolfcode.common.util.IdGenerateUtil;
import cn.wolfcode.domain.AccountLog;
import cn.wolfcode.domain.AccountTransaction;
import cn.wolfcode.domain.OperateIntegralVo;
import cn.wolfcode.domain.RefundVo;
import cn.wolfcode.mapper.AccountLogMapper;
import cn.wolfcode.mapper.AccountTransactionMapper;
import cn.wolfcode.mapper.UsableIntegralMapper;
import cn.wolfcode.service.IUsableIntegralService;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import io.seata.rm.tcc.api.BusinessActionContext;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.math.BigDecimal;
import java.util.Date;
import java.util.Map;


@Slf4j
@Service
public class UsableIntegralServiceImpl implements IUsableIntegralService {

    @Autowired
    private UsableIntegralMapper usableIntegralMapper;

    @Autowired
    private AccountTransactionMapper accountTransactionMapper;

    @Autowired
    private AccountLogMapper accountLogMapper;

    /**
     * 积分支付
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public String doPay(OperateIntegralVo operateIntegralVo) {
        // 1.直接使用当前需要扣除的用户积分，增加判断条件为剩余积分>0 不满足条件则积分不足
        int row = usableIntegralMapper.decrIntegral(operateIntegralVo.getUserId(), operateIntegralVo.getValue());
        AssertUtils.isTrue(row > 0, "账户积分不足");
        // 2.构建账户流水日志并保存
        AccountLog accountLog = new AccountLog();
        accountLog.setAmount(operateIntegralVo.getValue());
        accountLog.setInfo(operateIntegralVo.getInfo());
        accountLog.setGmtTime(new Date());
        accountLog.setOutTradeNo(operateIntegralVo.getOutTradeNo());
        accountLog.setTradeNo(IdGenerateUtil.get().nextId() + "");
        accountLog.setType(AccountLog.TYPE_DECR);
        accountLog.setUserId(operateIntegralVo.getUserId());
        accountLogMapper.insert(accountLog);
        return accountLog.getTradeNo();
    }

    /**
     * 积分退款
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean doRefund(RefundVo refundVo) {
        // 1.基于订单编号查询支付流水,类型必须是扣款类型
        AccountLog decrLog = accountLogMapper.selectByOutTradeNoAndType(refundVo.getOutTradeNo(), AccountLog.TYPE_DECR);
        AssertUtils.notNull(decrLog, "退款失败，改订单还未支付");
        // 2.通过支付流水中支付的金额 将其退回到用户的积分账户
        BigDecimal refundAmount = new BigDecimal(refundVo.getRefundAmount());
        int compareResult = refundAmount.compareTo(new BigDecimal(decrLog.getAmount()));
        AssertUtils.isTrue(compareResult <= 0, "退款金额不能大于支付金额");
        usableIntegralMapper.addIntegral(decrLog.getUserId(), refundAmount.longValue());
        // 3.记录新的退款流水记录
        AccountLog incrLog = new AccountLog();
        incrLog.setInfo(refundVo.getRefundReason());
        incrLog.setType(AccountLog.TYPE_INCR);
        incrLog.setAmount(refundAmount.longValue());
        incrLog.setTradeNo(IdGenerateUtil.get().nextId() + "");
        incrLog.setOutTradeNo(refundVo.getOutTradeNo());
        incrLog.setUserId(decrLog.getUserId());
        incrLog.setGmtTime(new Date());
        accountLogMapper.insert(incrLog);
        return true;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public String tryPayment(OperateIntegralVo vo, BusinessActionContext context) {
        log.info("[TCC-TRY] 收到请求参数：{}, 准备冻结金额：xid={}, branchId={}",
                JSON.toJSONString(vo), context.getXid(), context.getBranchId());
        // 直接生成支付流水订单编号
        String tradeNo = IdGenerateUtil.get().nextId() + "";
        // 1.实现防悬挂: 向数据库插入事务控制记录
        this.insertTxLog(vo, context, tradeNo, AccountLog.TYPE_DECR, AccountTransaction.STATE_TRY);
        // 2.冻结积分 判断是否冻结成功
        int row = usableIntegralMapper.freezeIntegral(vo.getUserId(), vo.getValue());
        AssertUtils.isTrue(row > 0, "账户余额不足");
        // 3.将tradeNo通过上下文对象传递给commit方法
        Map<String, Object> actionContextMap = context.getActionContext();
        actionContextMap.put("tradeNo", tradeNo);
        return tradeNo;
    }

    private void insertTxLog(OperateIntegralVo vo, BusinessActionContext context,
                             String tradeNo, Integer type, Integer state) {
        AccountTransaction tx = new AccountTransaction();
        tx.setAmount(vo.getValue());
        tx.setType(type);
        tx.setGmtCreated(new Date());
        tx.setTradeNo(tradeNo);
        tx.setUserId(vo.getUserId());
        tx.setState(state);
        tx.setGmtModified(new Date());
        tx.setTxId(context.getXid());
        tx.setActionId(context.getBranchId());
        accountTransactionMapper.insert(tx);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void commitPayment(BusinessActionContext context) {
        Object obj = context.getActionContext("vo");
        JSONObject vo = (JSONObject) obj;
        log.info("[TCC-CONFIRM] 收到TCC-CONFIRM消息 xid={}, branchId={}, 准备执行业务并提交本地事务:{}",
                context.getXid(), context.getBranchId(), obj);
        // 1.基于全局事务id和分支id获取唯一事务记录
        AccountTransaction tx = accountTransactionMapper.get(context.getXid(), context.getBranchId());
        // 2.判断否为空 如果为空说明一阶段都没执行过 流程异常
        if (tx == null) {
            log.warn("[TCC-CONFIRM] 流程异常，无法查询到try阶段事务记录，终止CONFIRM流程 xid={}, branchId={}",
                    context.getXid(), context.getBranchId());
            return;
        }
        // 3.幂等: 判断状态是否为已提交 如果已提交 保证幂等性即可
        if (AccountTransaction.STATE_COMMIT == tx.getState()) {
            log.warn("[TCC-CONFIRM] 流程异常，confirm方法出现幂等 被重复调用 xid={}, branchId={}",
                    context.getXid(), context.getBranchId());
            return;
        } else if (AccountTransaction.STATE_CANCEL == tx.getState()) {
            // 4.未提交 判断状态是否为已回滚 如果是 说明流程异常 方法结束
            log.warn("[TCC-CONFIRM] 流程异常，已经执行过cancel，无法再执行confirm; xid={}, branchId={}",
                    context.getXid(), context.getBranchId());
            return;
        }
        // 5.先真正扣除金额 (扣除冻结金额 + 账户余额)
        usableIntegralMapper.commitChange(vo.getLong("userId"), vo.getLong("value"));
        // 6.创建交易流水日志
        AccountLog accountLog = new AccountLog();
        accountLog.setAmount(vo.getLong("value"));
        accountLog.setInfo(vo.getString("info"));
        accountLog.setGmtTime(new Date());
        accountLog.setOutTradeNo(vo.getString("outTradeNo"));
        // 从上下文对象中获取交易流水号
//        String tradeNo = (String) context.getActionContext("tradeNo");
//        accountLog.setTradeNo(tradeNo);
        // 也可以在AccountTransaction中加入流水号字段 获取交易流水号
        accountLog.setTradeNo(tx.getTradeNo());
        accountLog.setType(AccountLog.TYPE_DECR);
        accountLog.setUserId(vo.getLong("userId"));
        accountLogMapper.insert(accountLog);
        // 7.将事务状态更新为已提交
        int row = accountTransactionMapper.updateAccountTransactionState(
                context.getXid(), context.getBranchId(),
                AccountTransaction.STATE_COMMIT, AccountTransaction.STATE_TRY);

        log.info("[TCC-CONFIRM] 积分支付提交操作执行完毕：{}", row);
        // 虽然抛异常但是会重试 需要做其他通知结束调方法
        AssertUtils.isTrue(row > 0, "事务并发修改异常");
//        if (row < 0) {
//            log.error("事务并发修改异常");
        // 这种方法给了错误提示没有抛异常不会重试 但是提交了本地事务不会回滚 会有脏数据
//        }

    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void rollbackPayment(BusinessActionContext context) {
        // 1.查询事务记录是否为空
        Object obj = context.getActionContext("vo");
        JSONObject vo = (JSONObject) obj;
        log.info("[TCC-CANCEL] 收到TCC-CANCEL消息 xid={}, branchId={}, 准备回滚TRY阶段操作:{}",
                context.getXid(), context.getBranchId(), obj);
        // 2.避免空回滚: 如果为空 插入一条cancel类型的事务记录并终止方法(空回滚的防护操作)
        AccountTransaction tx = accountTransactionMapper.get(context.getXid(), context.getBranchId());
        if (tx == null) {
            try {
                log.warn("[TCC-CANCEL] 流程异常，出现空回滚问题，一阶段(TRY)未执行 直接执行CANCEL xid={}, branchId={}",
                        context.getXid(), context.getBranchId());
                this.insertTxLog(vo.toJavaObject(OperateIntegralVo.class), context,
                        null, AccountLog.TYPE_DECR, AccountTransaction.STATE_CANCEL);
            } catch (Exception e) {
                log.warn("[TCC-CANCEL] 流程异常，出现空回滚并发问题，再插入空回滚记录前TRY先执行了 xid={}, branchId={}",
                        context.getXid(), context.getBranchId());
                log.error("[TCC-CANCEL] 流程异常信息", e);

            }
            // 出现空回滚以后 无论是否出现异常 都应该直接结束方法
            return;
        }
        // 3.如果不为空 判断事务状态是否为已回滚 如果是事务重复执行回滚操作 直接结束方法
        if (tx.getState() == AccountTransaction.STATE_CANCEL) {
            log.warn("[TCC-CANCEL] 流程异常，出现回滚重复执行问题，直接结束方法 xid={}, branchId={}",
                    context.getXid(), context.getBranchId());
            return;
        } else if (tx.getState() == AccountTransaction.STATE_COMMIT) {
            // 4.否则判断状态是否为已提交 如果是 说明流程异常结束方法
            log.warn("[TCC-CANCEL] 流程异常，已经执行过CONFIRM,不允许执行CANCEL方法 xid={}, branchId={}",
                    context.getXid(), context.getBranchId());
            return;
        }
        // 5.如果是初始化方法 正常流程 将之前的try方法执行的冻结金额回滚回去
        usableIntegralMapper.unFreezeIntegral(vo.getLong("userId"), vo.getLong("value"));
        // 修改事务记录状态为已回滚
        int row = accountTransactionMapper.updateAccountTransactionState(context.getXid(), context.getBranchId(),
                AccountTransaction.STATE_CANCEL, AccountTransaction.STATE_TRY);
        log.info("[TCC-CANCEL] 更新事务记录状态为已回滚：{}", row);
    }

}
